#include <iostream>
#include <exception>
#include "test_utils.h"
#include "priority_queue.h"

using std::string;

string location;
std::stringstream mystream;
using namespace mtm;

bool testPriorityQueueElement() {
	PriorityQueueElement<int, int> pqe(1, 2);
	ASSERT_EQUALS(1, pqe.priority);
	ASSERT_EQUALS(2, pqe.data);
	PriorityQueueElement<int, int> pqe2(1, 2);
	ASSERT_EQUALS(pqe, pqe2);
	pqe.priority = 2;
	ASSERT_NOT_EQUALS(pqe.priority, pqe2.priority);
	ASSERT_NOT_EQUALS(pqe, pqe2);
	pqe.data = 1;
	ASSERT_NOT_EQUALS(pqe.data, pqe2.data);
	ASSERT_NOT_EQUALS(pqe, pqe2);
	pqe.priority = 1;
	ASSERT_NOT_EQUALS(pqe, pqe2);
	ASSERT_TRUE(pqe != pqe2);
	mystream.str("");
	mystream << pqe;
	ASSERT_EQUALS(mystream.str(), "[1,1]");
	return true;
}

bool testIterator() {
	PriorityQueue<int, int> pq = PriorityQueue<int, int>();
	PriorityQueueElement<int, int> pqe(1, 2);
	pq.insert(pqe);
	PriorityQueue<int, int>::Iterator iter = pq.begin();
	ASSERT_EQUALS(*iter, pqe);
	PriorityQueue<int, int>::Iterator iter2 = pq.begin();
	ASSERT_EQUALS(iter, iter2);
	pq.insert(1, 3);
	PriorityQueue<int, int>::Iterator iter3 = pq.begin();
	ASSERT_NOT_EQUALS(iter, ++iter3);
	ASSERT_TRUE(iter != iter3);
	iter++;
	ASSERT_EQUALS(iter, iter3);
	iter = iter2;
	ASSERT_EQUALS(++iter, iter3);
	ASSERT_NOT_EQUALS(iter, iter2 + 2);
	iter3 += 1;
	ASSERT_EQUALS(iter3, iter2 + 2);
	mystream.str("");
	mystream << *iter;
	ASSERT_EQUALS(mystream.str(), "[1,3]");

	return true;
}

bool testConstIterator() {
	PriorityQueue<int, int> tmpPQ = PriorityQueue<int, int>();
	PriorityQueueElement<int, int> pqe(1, 2);
	tmpPQ.insert(pqe);
	const PriorityQueue<int, int> pq(tmpPQ);
	PriorityQueue<int, int>::const_iterator iter = pq.begin();
	ASSERT_EQUALS(*iter, pqe);
	PriorityQueue<int, int>::const_iterator iter2 = pq.begin();
	ASSERT_EQUALS(iter, iter2);
	tmpPQ.insert(1, 3);
	const PriorityQueue<int, int> pq2(tmpPQ);
	PriorityQueue<int, int>::const_iterator iter3 = pq2.begin();
	PriorityQueue<int, int>::const_iterator iter4 = pq2.begin();
	ASSERT_NOT_EQUALS(iter4, ++iter3);
	ASSERT_TRUE(iter4 != iter3);
	iter4++;
	ASSERT_EQUALS(iter4, iter3);
	iter4 = pq2.begin();
	ASSERT_EQUALS(iter4 + 1, iter3);
	iter4 += 2;
	ASSERT_EQUALS(iter4, ++iter3);
	mystream.str("");
	mystream << *iter;
	ASSERT_EQUALS(mystream.str(), "[1,2]");
	return true;
}

bool testPriorityQueueBasic() {
	PriorityQueue<int, int> pq;
	ASSERT_TRUE(pq.empty());
	ASSERT_EQUALS(pq.size(), 0);
	PriorityQueue<int, int> pqCopy(pq);
	ASSERT_EQUALS(pq.size(), pqCopy.size());
	PriorityQueue<int, int> pqCopy2 = PriorityQueue<int, int>();
	pqCopy2 = pq;
	ASSERT_EQUALS(pq.size(), pqCopy2.size());
	pq.insert(1, 2);
	ASSERT_EQUALS(pq.size(), 1);
	ASSERT_NO_THROW(pq[0]);
	ASSERT_THROWS(ElementAlreadyExists, pq.insert(1, 2));
	ASSERT_EQUALS(pq.size(), 1);
	ASSERT_NOT_EQUALS(pq.size(), pqCopy.size());
	pqCopy2 = pq;
	ASSERT_EQUALS(pq.size(), pqCopy2.size());
	ASSERT_NO_THROW(pq[0]);
	ASSERT_THROWS(IndexOutOfBounds, pq[-1]);
	ASSERT_THROWS(IndexOutOfBounds, pq[1]);
	ASSERT_EQUALS(pq[0], pq.top());
	mystream.str("");
	mystream << pq;
	ASSERT_EQUALS(mystream.str(), "[1,2]");
	ASSERT_NO_THROW(pq.pop());
	ASSERT_EQUALS(pq.size(), 0);
	return true;
}

bool testPriorityQueueInsert() {
	PriorityQueue<int, int> pq = PriorityQueue<int, int>();
	PriorityQueueElement<int, int> pqe(1, 2);
	ASSERT_NO_THROW(pq.insert(pqe));
	ASSERT_EQUALS(pq.size(), 1);
	ASSERT_EQUALS(pq[0].data, 2);
	ASSERT_THROWS(ElementAlreadyExists, pq.insert(1, 2));
	ASSERT_EQUALS(pq.size(), 1);
	ASSERT_NO_THROW(pq.insert(1, 1));
	ASSERT_EQUALS(pq.size(), 2);
	ASSERT_EQUALS(pq[1].data, 1);
	ASSERT_NO_THROW(pq.insert(-1, 3));
	ASSERT_EQUALS(pq.size(), 3);
	ASSERT_EQUALS(pq[2].data, 3);
	ASSERT_NO_THROW(pq.insert(2, 4));
	ASSERT_EQUALS(pq.size(), 4);
	ASSERT_EQUALS(pq[0].data, 4);
	return true;
}

bool testPriorityQueueErase() {
	PriorityQueue<int, int> pq = PriorityQueue<int, int>();
	ASSERT_THROWS(NoElements, pq.erase(pq.begin()));
	ASSERT_THROWS(NoElements, pq.erase(5));
	ASSERT_NO_THROW(pq.insert(1, 2));
	ASSERT_NO_THROW(pq.insert(-1, 9));
	ASSERT_NO_THROW(pq.insert(2, 4));
	ASSERT_NO_THROW(pq.insert(3, 3));
	PriorityQueue<int, int> pqCopy(pq);
	ASSERT_THROWS(IndexOutOfBounds, pq.erase(pqCopy.begin()));
	ASSERT_THROWS(IndexOutOfBounds, pq.erase(pqCopy.begin(), pqCopy.begin()));
	ASSERT_THROWS(IndexOutOfBounds,
			pq.erase(pqCopy.begin(), ++(pqCopy.begin())));
	ASSERT_THROWS(IndexOutOfBounds, pq.erase(pq.end(), pq.begin()));
	ASSERT_THROWS(ElementDoesNotExist, pq.erase(5));
	ASSERT_NO_THROW(pq.erase(pq.begin(), pq.begin()));
	ASSERT_EQUALS(pq.size(), 4);
	ASSERT_NO_THROW(pq.erase(pq.begin(), ++(pq.begin())));
	ASSERT_EQUALS(pq.size(), 3);
	ASSERT_NO_THROW(pq.erase(pq.begin()));
	ASSERT_EQUALS(pq.size(), 2);
	ASSERT_NO_THROW(pq.erase(2));
	ASSERT_EQUALS(pq.size(), 1);
	ASSERT_NO_THROW(pq.erase(pq.begin(), pq.end()));
	ASSERT_TRUE(pq.empty());
	return true;
}

bool testPriorityQueueTopPopGetEmptySize() {
	PriorityQueue<int, int> pq = PriorityQueue<int, int>();
	ASSERT_THROWS(NoElements, pq.top());
	ASSERT_THROWS(NoElements, pq.pop());
	ASSERT_THROWS(NoElements, pq[0]);
	ASSERT_THROWS(NoElements, pq[1]);
	ASSERT_TRUE(pq.empty());
	ASSERT_EQUALS(pq.size(), 0);
	ASSERT_NO_THROW(pq.insert(1, 2));
	ASSERT_FALSE(pq.empty());
	ASSERT_NO_THROW(pq.insert(2, 4));
	PriorityQueueElement<int, int> pqe(3, 3);
	ASSERT_NO_THROW(pq.insert(pqe));
	ASSERT_EQUALS(pq.size(), 3);
	ASSERT_EQUALS(pq.top(), pqe);
	ASSERT_EQUALS(pq[0], pqe);
	ASSERT_THROWS(IndexOutOfBounds, pq[3]);
	ASSERT_THROWS(IndexOutOfBounds, pq[-1]);
	ASSERT_NO_THROW(pq.pop());
	ASSERT_NO_THROW(pq.pop());
	ASSERT_NO_THROW(pq.pop());
	ASSERT_TRUE(pq.empty());
	return true;
}

class Modulu {
public:
	bool operator()(int priority) {
		return priority % 2 == 0;
	}
};

bool testPriorityQueuePrint() {
	PriorityQueue<int, int> pq = PriorityQueue<int, int>();
	mystream.str("");
	pq.print(mystream);
	ASSERT_EQUALS(mystream.str(), "");
	mystream.str("");
	pq.print(mystream, pq.begin(), pq.end());
	ASSERT_EQUALS(mystream.str(), "");
	mystream.str("");
	pq.print < Modulu > (mystream);
	ASSERT_EQUALS(mystream.str(), "");
	ASSERT_NO_THROW(pq.insert(1, 2));
	ASSERT_NO_THROW(pq.insert(-1, 9));
	ASSERT_NO_THROW(pq.insert(2, 4));
	ASSERT_NO_THROW(pq.insert(3, 3));
	mystream.str("");
	pq.print(mystream);
	ASSERT_EQUALS(mystream.str(), "[3,3][2,4][1,2][-1,9]");
	mystream.str("");
	pq.print(mystream, (pq.begin() + 1), (pq.begin() + 2));
	ASSERT_EQUALS(mystream.str(), "[2,4][1,2]");
	mystream.str("");
	pq.print < Modulu > (mystream);
	ASSERT_EQUALS(mystream.str(), "[2,4]");
	return true;
}

bool testPriorityQueueBeginEnd() {
	PriorityQueue<int, int> pq = PriorityQueue<int, int>();
	ASSERT_EQUALS(pq.begin(), pq.end());
	ASSERT_NO_THROW(pq.insert(1, 2));
	ASSERT_NO_THROW(pq.insert(-1, 9));
	ASSERT_NOT_EQUALS(pq.begin(), pq.end());
	PriorityQueue<int, int>::Iterator iter = pq.begin();
	iter++;
	ASSERT_NOT_EQUALS(iter, pq.end());
	iter++;
	ASSERT_EQUALS(iter, pq.end());
	const PriorityQueue<int, int> constPQ(pq);
	ASSERT_NOT_EQUALS(constPQ.begin(), constPQ.end());
	PriorityQueue<int, int>::const_iterator iter2 = constPQ.begin();
	iter2++;
	ASSERT_NOT_EQUALS(iter2, constPQ.end());
	iter2++;
	ASSERT_EQUALS(iter2, constPQ.end());
	return true;
}

int main() {
	RUN_TEST(testPriorityQueueElement);
	RUN_TEST(testIterator);
	RUN_TEST(testConstIterator);
	RUN_TEST(testPriorityQueueBasic);
	RUN_TEST(testPriorityQueueInsert);
	RUN_TEST(testPriorityQueueErase);
	RUN_TEST(testPriorityQueueTopPopGetEmptySize);
	RUN_TEST(testPriorityQueuePrint);
	RUN_TEST(testPriorityQueueBeginEnd);
	return 0;
}
